#define __BTSTACK_FILE__ "main.c"

#include "asf.h"
#include "stdio_serial.h"
#include "conf_board.h"
#include "conf_clock.h"

// BTstack
#include "btstack_chipset_atwilc3000.h"
#include "btstack_debug.h"
#include "btstack_memory.h"
#include "btstack_run_loop.h"
#include "btstack_run_loop_embedded.h"
#include "hal_uart_dma.h"
#include "hal_cpu.h"
#include "hal_tick.h"
#include "hci.h"
#include "hci_dump.h"
#include "hci_dump_embedded_stdout.h"
#include "hci_transport.h"
#include "hci_transport_h4.h"
#include "wilc3000_ble_firmware.h"

// #define USE_XDMAC_FOR_USART
#define XDMA_CH_UART_TX  0
#define XDMA_CH_UART_RX  1

/** All interrupt mask. */
#define ALL_INTERRUPT_MASK   0xffffffff
  
#ifdef __cplusplus
extern "C" {
#endif

extern int btstack_main(int argc, const char * argv[]);

static void dummy_handler(void){}
static void (*tick_handler)(void) = &dummy_handler;

static btstack_uart_config_t uart_config;

static hci_transport_config_uart_t transport_config = {
	HCI_TRANSPORT_CONFIG_UART,
	2000000,  // directly use high baud rate after config
	0, 		  // use 0 to skip baud rate change from 115200 to X for debugging purposes
	1,        // flow control
	NULL,
};

/**
 *  \brief Handler for System Tick interrupt.
 */
void SysTick_Handler(void)
{
	tick_handler();
}

// Debug console Output

/**
 *  Configure UART console.
 */
// [main_console_configure]
static void configure_console(void)
{
	const usart_serial_options_t uart_serial_options = {
		.baudrate = CONF_UART_BAUDRATE,
#ifdef CONF_UART_CHAR_LENGTH
		.charlength = CONF_UART_CHAR_LENGTH,
#endif
		.paritytype = CONF_UART_PARITY,
#ifdef CONF_UART_STOP_BITS
		.stopbits = CONF_UART_STOP_BITS,
#endif
	};

	/* Configure console UART. */
	sysclk_enable_peripheral_clock(CONSOLE_UART_ID);
	stdio_serial_init(CONF_UART, &uart_serial_options);
}

// Debug console Input

#include "btstack_stdin.h"

static void (*stdin_handler)(char c);
static btstack_data_source_t stdin_data_source;

static void btstack_stdin_process(struct btstack_data_source *ds, btstack_data_source_callback_type_t callback_type){
	// try to read from console
	uint32_t stdin_character;
	uint32_t res = usart_read(CONF_UART, &stdin_character);
	if (res) return;

	if (stdin_handler){
		(*stdin_handler)(stdin_character & 0xff);
	}
}

void btstack_stdin_setup(void (*handler)(char c)){
	// set handler
	stdin_handler = handler;

	// set up polling data_source
	btstack_run_loop_set_data_source_handler(&stdin_data_source, &btstack_stdin_process);
	btstack_run_loop_enable_data_source_callbacks(&stdin_data_source, DATA_SOURCE_CALLBACK_POLL);
	btstack_run_loop_add_data_source(&stdin_data_source);
}

// [main_console_configure]

/**
 * \brief Wait for the given number of milliseconds (ticks
 * generated by the SAM's microcontrollers's system tick).
 *
 * \param ul_dly_ticks  Delay to wait for, in milliseconds.
 */
// [main_ms_delay]
static void mdelay(uint32_t delay_in_ms)
{
	// delay_ms(delay_in_ms);
	uint32_t time_to_wait = btstack_run_loop_get_time_ms() + delay_in_ms;
	while (btstack_run_loop_get_time_ms() < time_to_wait);
}
// [main_ms_delay]

////////////////////////////////////////////////////////////////////////////////
// hal_cpu.h implementation
////////////////////////////////////////////////////////////////////////////////
// hal_led.h implementation
#include "hal_led.h"
void hal_led_off(void);
void hal_led_on(void);

void hal_led_off(void){
	// gpio_set_pin_low(GPIOA, GPIO_LED2);
}
void hal_led_on(void){
	// gpio_set_pin_high(GPIOA, GPIO_LED2);
}
void hal_led_toggle(void){
	// gpio_toggle_pin(GPIOA, GPIO_LED2);
}

// hal_cpu.h implementation
#include "hal_cpu.h"

void hal_cpu_disable_irqs(void){
	//__disable_irq();
}

void hal_cpu_enable_irqs(void){
	// __enable_irq();
}

void hal_cpu_enable_irqs_and_sleep(void){
	hal_led_off();
	// __enable_irq();
	// __asm__("wfe");	// go to sleep if event flag isn't set. if set, just clear it. IRQs set event flag

	// note: hal_uart_needed_during_sleep can be used to disable peripheral clock if it's not needed for a timer
	hal_led_on();
}


#ifndef USE_XDMAC_FOR_USART
// RX state
static volatile uint16_t  bytes_to_read = 0;
static volatile uint8_t * rx_buffer_ptr = 0;

// TX state
static volatile uint16_t  bytes_to_write = 0;
static volatile uint8_t * tx_buffer_ptr = 0;
#endif

static volatile int       rx_notify;
static volatile int       tx_notify;

static int simulate_flowcontrol;

// handlers
static void (*rx_done_handler)(void) = dummy_handler;
static void (*tx_done_handler)(void) = dummy_handler;
static void (*cts_irq_handler)(void) = dummy_handler;

// @note While the Atmel SAM S7x data sheet states
// "The hardware handshaking feature enables an out-of-band flow control by automatic management 
//  of the pins RTS and CTS.",
// I didn't see RTS going up automatically up, ever. So, at least for RTS, the automatic management
// is just a glorified GPIO pin control feature, which provides no benefit, but irritates a lot

// J505:6
#define DEBUG_PIN_1 PIO_PD16_IDX
// J505:5
#define DEBUG_PIN_2 PIO_PD15_IDX

static inline void hal_uart_rts_high(void){
	if (!simulate_flowcontrol) return;
	ioport_set_pin_level(DEBUG_PIN_2, IOPORT_PIN_LEVEL_HIGH);
	BOARD_USART->US_CR = US_CR_RTSEN;
}
static inline void hal_uart_rts_low(void){
	if (!simulate_flowcontrol) return;
	ioport_set_pin_level(DEBUG_PIN_2, IOPORT_PIN_LEVEL_LOW);
	BOARD_USART->US_CR = US_CR_RTSDIS;
}

/**
 */
static int hal_uart_dma_initialized = 0;
void hal_uart_dma_init(void)
{
	if (hal_uart_dma_initialized){
		log_info("hal_uart_dma_init already initialized");
		return;
	}
	hal_uart_dma_initialized = 1;

	// debug
#ifdef DEBUG_PIN_1
	ioport_set_pin_dir(DEBUG_PIN_1, IOPORT_DIR_OUTPUT);
	ioport_set_pin_level(DEBUG_PIN_1, IOPORT_PIN_LEVEL_LOW);
#endif
#ifdef DEBUG_PIN_2
	ioport_set_pin_dir(DEBUG_PIN_2, IOPORT_DIR_OUTPUT);
	ioport_set_pin_level(DEBUG_PIN_2, IOPORT_PIN_LEVEL_LOW);
#endif
	// power on
	ioport_set_pin_dir(BLUETOOTH_CHP_EN, IOPORT_DIR_OUTPUT);
	ioport_set_pin_level(BLUETOOTH_CHP_EN, IOPORT_PIN_LEVEL_HIGH);

	// reset
	ioport_set_pin_dir(BLUETOOTH_RESET, IOPORT_DIR_OUTPUT);
	ioport_set_pin_level(BLUETOOTH_RESET, IOPORT_PIN_LEVEL_LOW);
	mdelay(250);
	ioport_set_pin_level(BLUETOOTH_RESET, IOPORT_PIN_LEVEL_HIGH);
	mdelay(250);

	/* Enable the peripheral clock in the PMC. */
	sysclk_enable_peripheral_clock(BOARD_ID_USART);

	// configure Bluetooth USART
	const sam_usart_opt_t bluetooth_settings = {
		115200,
		US_MR_CHRL_8_BIT,
		US_MR_PAR_NO,
		US_MR_NBSTOP_1_BIT,
		US_MR_CHMODE_NORMAL,
		/* This field is only used in IrDA mode. */
		0
	};

	/* Configure USART mode. */
	simulate_flowcontrol = 0;
	usart_init_rs232(BOARD_USART, &bluetooth_settings, sysclk_get_peripheral_hz());
	// Set RTS = 0 (normal mode)
	BOARD_USART->US_CR = US_CR_RTSEN;

	/* Disable all the interrupts. */
	usart_disable_interrupt(BOARD_USART, ALL_INTERRUPT_MASK);

	/* Enable TX & RX function. */
	usart_enable_tx(BOARD_USART);
	usart_enable_rx(BOARD_USART);

	/* Configure and enable interrupt of USART. */
	NVIC_EnableIRQ(USART_IRQn);
	
#ifdef USE_XDMAC_FOR_USART

	// setup XDMAC
	
	/* Initialize and enable DMA controller */
	pmc_enable_periph_clk(ID_XDMAC);

	/* Enable XDMA interrupt */
	NVIC_ClearPendingIRQ(XDMAC_IRQn);
	NVIC_SetPriority( XDMAC_IRQn ,1);
	NVIC_EnableIRQ(XDMAC_IRQn);
	
	// Setup XDMA Channel for USART TX
	xdmac_channel_set_destination_addr(XDMAC, XDMA_CH_UART_TX, (uint32_t)&BOARD_USART->US_THR);
	xdmac_channel_set_config(XDMAC, XDMA_CH_UART_TX,
		XDMAC_CC_TYPE_PER_TRAN |
		XDMAC_CC_DSYNC_MEM2PER |
		XDMAC_CC_MEMSET_NORMAL_MODE |
		XDMAC_CC_MBSIZE_SINGLE |
		XDMAC_CC_DWIDTH_BYTE |
		XDMAC_CC_SIF_AHB_IF0 |
		XDMAC_CC_DIF_AHB_IF1 |
		XDMAC_CC_SAM_INCREMENTED_AM |
		XDMAC_CC_DAM_FIXED_AM |
		XDMAC_CC_CSIZE_CHK_1 |
		XDMAC_CC_PERID(XDAMC_CHANNEL_HWID_USART0_TX)
	);
	xdmac_channel_set_descriptor_control(XDMAC, XDMA_CH_UART_TX, 0);
	xdmac_channel_set_source_microblock_stride(XDMAC, XDMA_CH_UART_TX, 0);
	xdmac_channel_set_destination_microblock_stride(XDMAC, XDMA_CH_UART_TX, 0);
	xdmac_channel_set_datastride_mempattern(XDMAC, XDMA_CH_UART_TX, 0);
	xdmac_channel_set_block_control(XDMAC, XDMA_CH_UART_TX, 0);
	xdmac_enable_interrupt(XDMAC, XDMA_CH_UART_TX);
	xdmac_channel_enable_interrupt(XDMAC, XDMA_CH_UART_TX, XDMAC_CIE_BIE);
	
	// Setup XDMA Channel for USART RX
	xdmac_channel_set_source_addr(XDMAC, XDMA_CH_UART_RX, (uint32_t)&BOARD_USART->US_RHR);
	xdmac_channel_set_config(XDMAC, XDMA_CH_UART_RX,
		XDMAC_CC_TYPE_PER_TRAN |
		XDMAC_CC_DSYNC_PER2MEM |
		XDMAC_CC_MEMSET_NORMAL_MODE |
		XDMAC_CC_MBSIZE_SINGLE |
		XDMAC_CC_DWIDTH_BYTE |
		XDMAC_CC_SIF_AHB_IF1 |
		XDMAC_CC_DIF_AHB_IF0 |
		XDMAC_CC_SAM_FIXED_AM |
		XDMAC_CC_DAM_INCREMENTED_AM |
		XDMAC_CC_CSIZE_CHK_1 |
		XDMAC_CC_PERID(XDAMC_CHANNEL_HWID_USART0_RX)
	);
	xdmac_channel_set_descriptor_control(XDMAC, XDMA_CH_UART_RX, 0);
	xdmac_channel_set_source_microblock_stride(XDMAC, XDMA_CH_UART_RX, 0);
	xdmac_channel_set_destination_microblock_stride(XDMAC, XDMA_CH_UART_RX, 0);
	xdmac_channel_set_datastride_mempattern(XDMAC, XDMA_CH_UART_RX, 0);
	xdmac_channel_set_block_control(XDMAC, XDMA_CH_UART_RX, 0);
	xdmac_enable_interrupt(XDMAC, XDMA_CH_UART_RX);
	xdmac_channel_enable_interrupt(XDMAC, XDMA_CH_UART_RX, XDMAC_CIE_BIE);
#endif
}

void hal_uart_dma_set_sleep(uint8_t sleep){
}

void hal_uart_dma_set_block_received( void (*the_block_handler)(void)){
	rx_done_handler = the_block_handler;
}

void hal_uart_dma_set_block_sent( void (*the_block_handler)(void)){
	tx_done_handler = the_block_handler;
}

void hal_uart_dma_set_csr_irq_handler( void (*the_irq_handler)(void)){
	cts_irq_handler = the_irq_handler;
}

int  hal_uart_dma_set_baud(uint32_t baud){
	/* Disable TX & RX function. */
	usart_disable_tx(BOARD_USART);
	usart_disable_rx(BOARD_USART);
	uint32_t res = usart_set_async_baudrate(BOARD_USART, baud, sysclk_get_peripheral_hz());
	if (res){
		log_error("hal_uart_dma_set_baud library call failed");
	}
	
	/* Enable TX & RX function. */
	usart_enable_tx(BOARD_USART);
	usart_enable_rx(BOARD_USART);

	log_info("set baud rate %u", (int) baud);
	return 0;
}

int  hal_uart_dma_set_flowcontrol(int flowcontrol){
	log_info("hal_uart_dma_set_flowcontrol %u", flowcontrol);
	simulate_flowcontrol = flowcontrol;
	if (flowcontrol){
		/* Set hardware handshaking mode. */
		BOARD_USART->US_MR = (BOARD_USART->US_MR & ~US_MR_USART_MODE_Msk) | US_MR_USART_MODE_HW_HANDSHAKING;
		hal_uart_rts_high();
	} else {
		/* Set nomal mode. */
		BOARD_USART->US_MR = (BOARD_USART->US_MR & ~US_MR_USART_MODE_Msk) | US_MR_USART_MODE_NORMAL;
		// Set RTS = 0 (normal mode)
		BOARD_USART->US_CR = US_CR_RTSEN;
	}
	return 0;
}

void hal_uart_dma_send_block(const uint8_t *data, uint16_t size){

	tx_notify = 1;

#ifdef USE_XDMAC_FOR_USART
	xdmac_channel_get_interrupt_status( XDMAC, XDMA_CH_UART_TX);
	xdmac_channel_set_source_addr(XDMAC, XDMA_CH_UART_TX, (uint32_t)data);
	xdmac_channel_set_microblock_control(XDMAC, XDMA_CH_UART_TX, size);
	xdmac_channel_enable(XDMAC, XDMA_CH_UART_TX);
#else
	if (bytes_to_write){
		log_error("send block, bytes to write %u", bytes_to_write);
		return;
	}
    tx_buffer_ptr = (uint8_t *) data;
    bytes_to_write = size;
	usart_enable_interrupt(BOARD_USART, US_IER_TXRDY);
#endif
}

void hal_uart_dma_receive_block(uint8_t *data, uint16_t size){

#ifdef DEBUG_PIN_1
	ioport_set_pin_level(DEBUG_PIN_1, IOPORT_PIN_LEVEL_HIGH);
#endif

	hal_uart_rts_low();

	rx_notify = 1;

#ifdef USE_XDMAC_FOR_USART
	xdmac_channel_get_interrupt_status( XDMAC, XDMA_CH_UART_RX);
	xdmac_channel_set_destination_addr(XDMAC, XDMA_CH_UART_RX, (uint32_t)data);
	xdmac_channel_set_microblock_control(XDMAC, XDMA_CH_UART_RX, size);
	xdmac_channel_enable(XDMAC, XDMA_CH_UART_RX);
#else
    rx_buffer_ptr = data;
    bytes_to_read = size;
    usart_enable_interrupt(BOARD_USART, US_IER_RXRDY);
#endif
}

#ifdef USE_XDMAC_FOR_USART
void XDMAC_Handler(void)
{
	uint32_t dma_status;
	dma_status = xdmac_channel_get_interrupt_status(XDMAC, XDMA_CH_UART_TX);
	if (dma_status & XDMAC_CIS_BIS) {
		if (tx_notify){
			tx_notify = 0;
			tx_done_handler();
		}
	}
	dma_status = xdmac_channel_get_interrupt_status(XDMAC, XDMA_CH_UART_RX);
	if (dma_status & XDMAC_CIS_BIS) {
		hal_uart_rts_high();
		if (rx_notify){
			rx_notify = 0;
			rx_done_handler();
		}
	}
}
#else
void USART_Handler(void)
{

#ifdef DEBUG_PIN_2
	// ioport_set_pin_level(DEBUG_PIN_2, IOPORT_PIN_LEVEL_HIGH);
#endif

	/* Read USART status. */
	uint32_t ul_status = usart_get_status(BOARD_USART);

	// handle ready to send
	if(ul_status & US_IER_TXRDY) {
		if (bytes_to_write){
			// send next byte
			usart_write(BOARD_USART, *tx_buffer_ptr);
			tx_buffer_ptr++;
			bytes_to_write--;
		} else {

			// done. disable tx ready interrupt to avoid starvation here
			usart_disable_interrupt(BOARD_USART, US_IER_TXRDY);
			if (tx_notify){
				tx_notify = 0;
				tx_done_handler();
			}
		}
	}

	// handle byte available for read
	if (ul_status & US_IER_RXRDY) {
		if (bytes_to_read){
			uint32_t ch;
			usart_read(BOARD_USART, (uint32_t *)&ch);
			*rx_buffer_ptr++ = ch;
			bytes_to_read--;
			if (bytes_to_read == 0){

#ifdef DEBUG_PIN_1
			ioport_set_pin_level(DEBUG_PIN_1, IOPORT_PIN_LEVEL_LOW);
#endif

				// done. disable rx ready interrupt, raise RTS
				hal_uart_rts_high();
				usart_disable_interrupt(BOARD_USART, US_IER_RXRDY);
				if (rx_notify){
					rx_notify = 0;
					rx_done_handler();
				}
			}
		} else {
			// shoult not happen, disable irq anyway
			usart_disable_interrupt(BOARD_USART, US_IER_RXRDY);
		}
	}
#ifdef DEBUG_PIN_2
	// ioport_set_pin_level(DEBUG_PIN_2, IOPORT_PIN_LEVEL_LOW);
#endif

}
#endif

void hal_tick_init()
{
	/* Configure systick for 1 ms */
	puts("Configure system tick to get 1ms tick period.\r");
	if (SysTick_Config(sysclk_get_cpu_hz() / 1000)) {
		puts("-F- Systick configuration error\r");
		while (1);
	}
}

void hal_tick_set_handler(void (*handler)(void)){
	if (handler == NULL){
		tick_handler = &dummy_handler;
		return;
	}
	tick_handler = handler;
}

int  hal_tick_get_tick_period_in_ms(void){
	return 1;
}

static const btstack_uart_block_t * uart_driver;

static void phase2(int status){

    if (status){
        printf("Download firmware failed\n");
        return;
    }

    printf("Phase 2: Main app\n");

    // init HCI
    const hci_transport_t * transport = hci_transport_h4_instance(uart_driver);
    hci_init(transport, (void*) &transport_config);
    hci_set_chipset(btstack_chipset_atwilc3000_instance());
    
    // setup app
    btstack_main(0, NULL);
}

/**
 *  \brief getting-started Application entry point.
 *
 *  \return Unused (ANSI-C compatibility).
 */
// [main]
int main(void)
{
	/* Initialize the SAM system */
	sysclk_init();
	board_init();

	/* Initialize the console uart */
	configure_console();

	/* Output boot info */
	printf("BTstack on SAMV71 Xplained Ultra with ATWILC3000\n");
	printf("CPU %lu hz, peripheral clock %lu hz\n", sysclk_get_cpu_hz(), sysclk_get_peripheral_hz());
#ifdef USE_XDMAC_FOR_USART
	printf("Using XDMA for Bluetooth UART\n");
#else
	printf("Using IRQ driver for Bluetooth UART\n");
#endif
	printf("--\n");

	// start with BTstack init - especially configure HCI Transport
	btstack_memory_init();
	btstack_run_loop_init(btstack_run_loop_embedded_get_instance());

	// enable full log output while porting
    // hci_dump_init(hci_dump_embedded_stdout_get_instance());

	// setup UART HAL + Run Loop integration
	uart_driver = btstack_uart_block_embedded_instance();

    // extract UART config from transport config, but disable flow control and use default baudrate
    uart_config.baudrate    = HCI_DEFAULT_BAUDRATE;
    uart_config.flowcontrol = 0;
    uart_config.device_name = transport_config.device_name;
    uart_driver->init(&uart_config);

    // phase #1 download firmware
    printf("Phase 1: Download firmware\n");

    // phase #2 start main app
    btstack_chipset_atwilc3000_download_firmware(uart_driver, transport_config.baudrate_init, transport_config.flowcontrol,  (const uint8_t *) firmware_ble, sizeof(firmware_ble), &phase2);

	// go
	btstack_run_loop_execute();

	// compiler happy
	while(1);
}
#ifdef __cplusplus
}
#endif
